/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include <signal.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/types.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#ifndef DEDICATED
#ifdef USE_LOCAL_HEADERS
#	include "SDL.h"
#	include "SDL_cpuinfo.h"
#else
#	include <SDL.h>
#	include <SDL_cpuinfo.h>
#endif
#endif

#include "sys_local.h"
#include "sys_loadlib.h"

#include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h"

static char     binaryPath[MAX_OSPATH] = { 0 };
static char     installPath[MAX_OSPATH] = { 0 };

/*
=================
Sys_SetBinaryPath
=================
*/
void Sys_SetBinaryPath(const char *path)
{
	Q_strncpyz(binaryPath, path, sizeof(binaryPath));
}

/*
=================
Sys_BinaryPath
=================
*/
char           *Sys_BinaryPath(void)
{
	return binaryPath;
}

/*
=================
Sys_SetDefaultInstallPath
=================
*/
void Sys_SetDefaultInstallPath(const char *path)
{
	Q_strncpyz(installPath, path, sizeof(installPath));
}

/*
=================
Sys_DefaultInstallPath
=================
*/
char           *Sys_DefaultInstallPath(void)
{
	if(*installPath)
		return installPath;
	else
		return Sys_Cwd();
}

/*
=================
Sys_DefaultAppPath
=================
*/
char           *Sys_DefaultAppPath(void)
{
	return Sys_BinaryPath();
}

/*
=================
Sys_In_Restart_f

Restart the input subsystem
=================
*/
void Sys_In_Restart_f(void)
{
	IN_Restart();
}

/*
=================
Sys_ConsoleInput

Handle new console input
=================
*/
char           *Sys_ConsoleInput(void)
{
	return CON_Input();
}

#ifdef DEDICATED
#	define PID_FILENAME PRODUCT_NAME_UPPPER "_server.pid"
#else
#	define PID_FILENAME PRODUCT_NAME_UPPPER ".pid"
#endif

/*
=================
Sys_PIDFileName
=================
*/
static char    *Sys_PIDFileName(void)
{
	return va("%s/%s", Sys_TempPath(), PID_FILENAME);
}

/*
=================
Sys_WritePIDFile

Return qtrue if there is an existing stale PID file
=================
*/
qboolean Sys_WritePIDFile(void)
{
	char           *pidFile = Sys_PIDFileName();
	FILE           *f;
	qboolean        stale = qfalse;

	// First, check if the pid file is already there
	if((f = fopen(pidFile, "r")) != NULL)
	{
		char            pidBuffer[64] = { 0 };
		int             pid;

		fread(pidBuffer, sizeof(char), sizeof(pidBuffer) - 1, f);
		fclose(f);

		pid = atoi(pidBuffer);
		if(!Sys_PIDIsRunning(pid))
			stale = qtrue;
	}

	if((f = fopen(pidFile, "w")) != NULL)
	{
		fprintf(f, "%d", Sys_PID());
		fclose(f);
	}
	else
		Com_Printf(S_COLOR_YELLOW "Couldn't write %s.\n", pidFile);

	return stale;
}

/*
=================
Sys_Exit

Single exit point (regular exit or in case of error)
=================
*/
static void Sys_Exit(int exitCode)
{
	CON_Shutdown();

#ifndef DEDICATED
	SDL_Quit();
#endif

	if(exitCode < 2)
	{
		// Normal exit
		remove(Sys_PIDFileName());
	}

#ifdef NDEBUG
	exit(exitCode);
#else
	// Cause a backtrace on error exits
	assert(exitCode == 0);
	exit(exitCode);
#endif
}

/*
=================
Sys_Quit
=================
*/
void Sys_Quit(void)
{
	Sys_Exit(0);
}

/*
=================
Sys_GetProcessorFeatures
=================
*/
cpuFeatures_t Sys_GetProcessorFeatures(void)
{
	cpuFeatures_t   features = 0;

#ifndef DEDICATED
	if(SDL_HasRDTSC())
		features |= CF_RDTSC;
	if(SDL_HasMMX())
		features |= CF_MMX;
	if(SDL_HasMMXExt())
		features |= CF_MMX_EXT;
	if(SDL_Has3DNow())
		features |= CF_3DNOW;
	if(SDL_Has3DNowExt())
		features |= CF_3DNOW_EXT;
	if(SDL_HasSSE())
		features |= CF_SSE;
	if(SDL_HasSSE2())
		features |= CF_SSE2;
	if(SDL_HasAltiVec())
		features |= CF_ALTIVEC;
#endif

	return features;
}

/*
=================
Sys_Init
=================
*/
void Sys_Init(void)
{
	Cmd_AddCommand("in_restart", Sys_In_Restart_f);
	Cvar_Set("arch", OS_STRING " " ARCH_STRING);
	Cvar_Set("username", Sys_GetCurrentUser());
}

/*
=================
Sys_AnsiColorPrint

Transform Q3 colour codes to ANSI escape sequences
=================
*/
void Sys_AnsiColorPrint(const char *msg)
{
	static char     buffer[MAXPRINTMSG];
	int             length = 0;
	static int      q3ToAnsi[8] = {
		30,						// COLOR_BLACK
		31,						// COLOR_RED
		32,						// COLOR_GREEN
		33,						// COLOR_YELLOW
		34,						// COLOR_BLUE
		36,						// COLOR_CYAN
		35,						// COLOR_MAGENTA
		0						// COLOR_WHITE
	};

	while(*msg)
	{
		if(Q_IsColorString(msg) || *msg == '\n')
		{
			// First empty the buffer
			if(length > 0)
			{
				buffer[length] = '\0';
				fputs(buffer, stderr);
				length = 0;
			}

			if(*msg == '\n')
			{
				// Issue a reset and then the newline
				fputs("\033[0m\n", stderr);
				msg++;
			}
			else
			{
				// Print the color code
				Com_sprintf(buffer, sizeof(buffer), "\033[%dm", q3ToAnsi[ColorIndex(*(msg + 1))]);
				fputs(buffer, stderr);
				msg += 2;
			}
		}
		else
		{
			if(length >= MAXPRINTMSG - 1)
				break;

			buffer[length] = *msg;
			length++;
			msg++;
		}
	}

	// Empty anything still left in the buffer
	if(length > 0)
	{
		buffer[length] = '\0';
		fputs(buffer, stderr);
	}
}

/*
=================
Sys_Print
=================
*/
void Sys_Print(const char *msg)
{
	CON_LogWrite(msg);
	CON_Print(msg);
}

/*
=================
Sys_SetConsoleVisibility
=================
*/
void Sys_SetConsoleVisibility(int visLevel)
{
	CON_SetVisibility(visLevel);
}

/*
=================
Sys_Error
=================
*/
void Sys_Error(const char *error, ...)
{
	va_list         argptr;
	char            string[1024];

	va_start(argptr, error);
	Q_vsnprintf(string, sizeof(string), error, argptr);
	va_end(argptr);

	CL_Shutdown(string);
	Sys_ErrorDialog(string);

	Sys_Exit(3);
}

/*
=================
Sys_Warn
=================
*/
void Sys_Warn(char *warning, ...)
{
	va_list         argptr;
	char            string[1024];

	va_start(argptr, warning);
	Q_vsnprintf(string, sizeof(string), warning, argptr);
	va_end(argptr);

	CON_Print(va("Warning: %s", string));
}

/*
============
Sys_FileTime

returns -1 if not present
============
*/
int Sys_FileTime(char *path)
{
	struct stat     buf;

	if(stat(path, &buf) == -1)
		return -1;

	return buf.st_mtime;
}

/*
=================
Sys_UnloadDll
=================
*/
void Sys_UnloadDll(void *dllHandle)
{
	if(!dllHandle)
	{
		Com_Printf("Sys_UnloadDll(NULL)\n");
		return;
	}

	Sys_UnloadLibrary(dllHandle);
}

/*
=================
Sys_TryLibraryLoad
=================
*/
static void    *Sys_TryLibraryLoad(const char *base, const char *gamedir, const char *fname, char *fqpath)
{
	void           *libHandle;
	char           *fn;

	*fqpath = 0;

	fn = FS_BuildOSPath(base, gamedir, fname);
	Com_Printf("Sys_LoadDll(%s)... \n", fn);

	libHandle = Sys_LoadLibrary(fn);

	if(!libHandle)
	{
		Com_Printf("Sys_LoadDll(%s) failed:\n\"%s\"\n", fn, Sys_LibraryError());
		return NULL;
	}

	Com_Printf("Sys_LoadDll(%s): succeeded ...\n", fn);
	Q_strncpyz(fqpath, fn, MAX_QPATH);

	return libHandle;
}

/*
=================
Sys_LoadDll

Used to load a development dll instead of a virtual machine
#1 look down current path
#2 look in fs_homepath
#3 look in fs_basepath
=================
*/
void           *Sys_LoadDll(const char *name, char *fqpath,
							intptr_t(**entryPoint) (int, ...), intptr_t(*systemcalls) (intptr_t, ...))
{
	void           *libHandle;
	void            (*dllEntry) (intptr_t(*syscallptr) (intptr_t, ...));
	char            fname[MAX_OSPATH];
	char           *basepath;
	char           *homepath;
	char           *pwdpath;
	char           *gamedir;

	assert(name);

	Q_snprintf(fname, sizeof(fname), "%s" ARCH_STRING DLL_EXT, name);

	// TODO: use fs_searchpaths from files.c
	pwdpath = Sys_Cwd();
	basepath = Cvar_VariableString("fs_basepath");
	homepath = Cvar_VariableString("fs_homepath");
	gamedir = Cvar_VariableString("fs_game");

	libHandle = Sys_TryLibraryLoad(pwdpath, gamedir, fname, fqpath);

	if(!libHandle && homepath)
		libHandle = Sys_TryLibraryLoad(homepath, gamedir, fname, fqpath);

	if(!libHandle && basepath)
		libHandle = Sys_TryLibraryLoad(basepath, gamedir, fname, fqpath);

	if(!libHandle)
	{
		Com_Printf("Sys_LoadDll(%s) failed to load library\n", name);
		return NULL;
	}

	dllEntry = Sys_LoadFunction(libHandle, "dllEntry");
	*entryPoint = Sys_LoadFunction(libHandle, "vmMain");

	if(!*entryPoint || !dllEntry)
	{
		Com_Printf("Sys_LoadDll(%s) failed to find vmMain function:\n\"%s\" !\n", name, Sys_LibraryError());
		Sys_UnloadLibrary(libHandle);

		return NULL;
	}

	Com_Printf("Sys_LoadDll(%s) found vmMain function at %p\n", name, *entryPoint);
	dllEntry(systemcalls);

	return libHandle;
}

/*
=================
Sys_ParseArgs
=================
*/
void Sys_ParseArgs(int argc, char **argv)
{
	if(argc == 2)
	{
		if(!strcmp(argv[1], "--version") || !strcmp(argv[1], "-v"))
		{
			const char     *date = __DATE__;

#ifdef DEDICATED
			fprintf(stdout, Q3_VERSION " dedicated server (%s)\n", date);
#else
			fprintf(stdout, Q3_VERSION " client (%s)\n", date);
#endif
			fprintf(stdout, Q3_ENGINE " engine (%s)\n", Q3_ENGINE_DATE);
			Sys_Exit(0);
		}
	}
}

#ifndef DEFAULT_BASEDIR
#	ifdef MACOS_X
#		define DEFAULT_BASEDIR Sys_StripAppBundle(Sys_BinaryPath())
#	else
#		define DEFAULT_BASEDIR Sys_BinaryPath()
#	endif
#endif

/*
=================
Sys_SigHandler
=================
*/
void Sys_SigHandler(int signal)
{
	static qboolean signalcaught = qfalse;

	if(signalcaught)
	{
		fprintf(stderr, "DOUBLE SIGNAL FAULT: Received signal %d, exiting...\n", signal);
	}
	else
	{
		signalcaught = qtrue;
#ifndef DEDICATED
		CL_Shutdown(va("Received signal %d", signal));
#endif
		SV_Shutdown(va("Received signal %d", signal));
	}

	if(signal == SIGTERM || signal == SIGINT)
		Sys_Exit(1);
	else
		Sys_Exit(2);
}

/*
=================
main
=================
*/
int main(int argc, char **argv)
{
	int             i;
	char            commandLine[MAX_STRING_CHARS] = { 0 };

#ifndef DEDICATED
	// SDL version check

	// Compile time
#	if !SDL_VERSION_ATLEAST(MINSDL_MAJOR,MINSDL_MINOR,MINSDL_PATCH)
#		error A more recent version of SDL is required
#	endif

	// Run time
	const SDL_version *ver = SDL_Linked_Version();

#define MINSDL_VERSION \
	XSTRING(MINSDL_MAJOR) "." \
	XSTRING(MINSDL_MINOR) "." \
	XSTRING(MINSDL_PATCH)

	if(SDL_VERSIONNUM(ver->major, ver->minor, ver->patch) < SDL_VERSIONNUM(MINSDL_MAJOR, MINSDL_MINOR, MINSDL_PATCH))
	{
		Sys_Dialog(DT_ERROR, va("SDL version " MINSDL_VERSION " or greater is required, "
								"but only version %d.%d.%d was found. You may be able to obtain a more recent copy "
								"from http://www.libsdl.org/.", ver->major, ver->minor, ver->patch), "SDL Library Too Old");

		Sys_Exit(1);
	}
#endif

	Sys_PlatformInit();

#if defined(USE_JAVA)
	// Tr3B: Java likes to change the printf formatting, e.g. decimal numbers use ',' instead of '.'
	// with a de_DE locale setting. This can break the GLSL compilers in the drivers.
	// As a side note: It only happens with Java remote debugging enabled "+set jvm_remoteDebugging 1".
	Sys_SetEnv("LC_NUMERIC", "en_US");
#endif

	// Set the initial time base
	Sys_Milliseconds();

	Sys_ParseArgs(argc, argv);
	Sys_SetBinaryPath(Sys_Dirname(argv[0]));
	Sys_SetDefaultInstallPath(DEFAULT_BASEDIR);

	// Concatenate the command line for passing to Com_Init
	for(i = 1; i < argc; i++)
	{
		const qboolean  containsSpaces = strchr(argv[i], ' ') != NULL;

		if(containsSpaces)
			Q_strcat(commandLine, sizeof(commandLine), "\"");

		Q_strcat(commandLine, sizeof(commandLine), argv[i]);

		if(containsSpaces)
			Q_strcat(commandLine, sizeof(commandLine), "\"");

		Q_strcat(commandLine, sizeof(commandLine), " ");
	}

	// done before Com/Sys_Init since we need this for error output
	CON_Init();

	Com_Init(commandLine);
	NET_Init();

	// hide the early console since we've reached the point where we
	// have a working graphics subsystems
	//if(!com_dedicated->integer && !com_viewlog->integer)
	{
		CON_SetVisibility(0);
	}

	// Tr3B: don't set the signal handlers
	// as it effectively breaks all debugging features
#if 0//!defined(USE_JAVA)
	signal(SIGILL, Sys_SigHandler);
	signal(SIGFPE, Sys_SigHandler);
	signal(SIGSEGV, Sys_SigHandler);
	signal(SIGTERM, Sys_SigHandler);
	signal(SIGINT, Sys_SigHandler);
#endif

	while(1)
	{
#if 0//ndef DEDICATED
		int             appState = SDL_GetAppState();

		Cvar_SetValue("com_unfocused", !(appState & SDL_APPINPUTFOCUS));
		Cvar_SetValue("com_minimized", !(appState & SDL_APPACTIVE));
#endif

		IN_Frame();
		Com_Frame();
	}

	return 0;
}
